feat: Implement BerkeleyDB reader for RPM databases
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
console-runner-image / build-runner-image (push) Has been cancelled
wine-csp-build / Build Wine CSP Image (push) Has been cancelled
wine-csp-build / Integration Tests (push) Has been cancelled
wine-csp-build / Security Scan (push) Has been cancelled
wine-csp-build / Generate SBOM (push) Has been cancelled
wine-csp-build / Publish Image (push) Has been cancelled
wine-csp-build / Air-Gap Bundle (push) Has been cancelled
wine-csp-build / Test Summary (push) Has been cancelled

- Added BerkeleyDbReader class to read and extract RPM header blobs from BerkeleyDB hash databases.
- Implemented methods to detect BerkeleyDB format and extract values, including handling of page sizes and magic numbers.
- Added tests for BerkeleyDbReader to ensure correct functionality and header extraction.

feat: Add Yarn PnP data tests

- Created YarnPnpDataTests to validate package resolution and data loading from Yarn PnP cache.
- Implemented tests for resolved keys, package presence, and loading from cache structure.

test: Add egg-info package fixtures for Python tests

- Created egg-info package fixtures for testing Python analyzers.
- Included PKG-INFO, entry_points.txt, and installed-files.txt for comprehensive coverage.

test: Enhance RPM database reader tests

- Added tests for RpmDatabaseReader to validate fallback to legacy packages when SQLite is missing.
- Implemented helper methods to create legacy package files and RPM headers for testing.

test: Implement dual signing tests

- Added DualSignTests to validate secondary signature addition when configured.
- Created stub implementations for crypto providers and key resolvers to facilitate testing.

chore: Update CI script for Playwright Chromium installation

- Modified ci-console-exports.sh to ensure deterministic Chromium binary installation for console exports tests.
- Added checks for Windows compatibility and environment variable setups for Playwright browsers.
This commit is contained in:
StellaOps Bot
2025-12-07 16:24:45 +02:00
parent e3f28a21ab
commit 11597679ed
199 changed files with 9809 additions and 4404 deletions

View File

@@ -2,8 +2,8 @@ using System.Text.Json;
using MongoDB.Bson;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Concelier.Storage.Mongo.Dtos;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo;
namespace StellaOps.Concelier.Connector.Osv.Tests;

View File

@@ -5,25 +5,25 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.RegularExpressions;
using MongoDB.Bson;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Concelier.Storage.Mongo.Dtos;
using StellaOps.Cryptography;
using Xunit;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Cryptography;
using Xunit;
namespace StellaOps.Concelier.Connector.Osv.Tests;
public sealed class OsvGhsaParityRegressionTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
private static readonly ICryptoHash Hash = CryptoHashFactory.CreateDefault();
public sealed class OsvGhsaParityRegressionTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
private static readonly ICryptoHash Hash = CryptoHashFactory.CreateDefault();
// Curated GHSA identifiers spanning multiple ecosystems (PyPI, npm/go, Maven) for parity coverage.
private static readonly string[] GhsaIds =
@@ -561,7 +561,7 @@ public sealed class OsvGhsaParityRegressionTests
private static string ComputeSha256Hex(string payload)
{
var bytes = Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payload), HashAlgorithms.Sha256);
var bytes = Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payload), HashAlgorithms.Sha256);
return Convert.ToHexString(bytes);
}

View File

@@ -1,28 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Reflection;
using MongoDB.Bson;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Normalization.Identifiers;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Concelier.Storage.Mongo.Dtos;
using Xunit;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Reflection;
using MongoDB.Bson;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Normalization.Identifiers;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo;
using Xunit;
namespace StellaOps.Concelier.Connector.Osv.Tests;
public sealed class OsvMapperTests
{
[Fact]
public void Map_NormalizesAliasesReferencesAndRanges()
{
var published = DateTimeOffset.UtcNow.AddDays(-2);
var modified = DateTimeOffset.UtcNow.AddDays(-1);
public void Map_NormalizesAliasesReferencesAndRanges()
{
var published = DateTimeOffset.UtcNow.AddDays(-2);
var modified = DateTimeOffset.UtcNow.AddDays(-1);
using var databaseSpecificJson = JsonDocument.Parse("{}");
using var ecosystemSpecificJson = JsonDocument.Parse("{}");
@@ -120,121 +120,121 @@ public sealed class OsvMapperTests
Assert.Equal("1.0.1", semver.Fixed);
Assert.False(semver.FixedInclusive);
Assert.Single(advisory.CvssMetrics);
Assert.Equal("3.1", advisory.CvssMetrics[0].Version);
}
[Fact]
public void Map_AssignsSeverityFallbackWhenCvssVectorUnsupported()
{
using var databaseSpecificJson = JsonDocument.Parse("""
{
"severity": "MODERATE",
"cwe_ids": ["CWE-290"]
}
""");
var dto = new OsvVulnerabilityDto
{
Id = "OSV-CVSS4",
Summary = "Severity-only advisory",
Details = "OSV entry that lacks a parsable CVSS vector.",
Published = DateTimeOffset.UtcNow.AddDays(-10),
Modified = DateTimeOffset.UtcNow.AddDays(-5),
DatabaseSpecific = databaseSpecificJson.RootElement,
Severity = new[]
{
new OsvSeverityDto
{
Type = "CVSS_V4",
Score = "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"
}
}
};
var (document, dtoRecord) = CreateDocumentAndDtoRecord(dto, "PyPI");
var advisory = OsvMapper.Map(dto, document, dtoRecord, "PyPI");
Assert.True(advisory.CvssMetrics.IsEmpty);
Assert.Equal("medium", advisory.Severity);
Assert.Equal("osv:severity/medium", advisory.CanonicalMetricId);
var weakness = Assert.Single(advisory.Cwes);
var provenance = Assert.Single(weakness.Provenance);
Assert.Equal("database_specific.cwe_ids", provenance.DecisionReason);
}
[Theory]
[InlineData("Go", "github.com/example/project", "pkg:golang/github.com/example/project")]
[InlineData("PyPI", "social_auth_app_django", "pkg:pypi/social-auth-app-django")]
[InlineData("npm", "@Scope/Package", "pkg:npm/%40scope/package")]
[InlineData("Maven", "org.example:library", "pkg:maven/org.example/library")]
[InlineData("crates", "serde", "pkg:cargo/serde")]
public void Map_InfersCanonicalPackageUrlWhenPurlMissing(string ecosystem, string packageName, string expectedIdentifier)
{
var dto = new OsvVulnerabilityDto
{
Id = $"OSV-{ecosystem}-PURL",
Summary = "Test advisory",
Details = "Details",
Published = DateTimeOffset.UtcNow.AddDays(-1),
Modified = DateTimeOffset.UtcNow,
Affected = new[]
{
new OsvAffectedPackageDto
{
Package = new OsvPackageDto
{
Ecosystem = ecosystem,
Name = packageName,
Purl = null,
},
Ranges = null,
}
}
};
if (string.Equals(ecosystem, "npm", StringComparison.OrdinalIgnoreCase))
{
Assert.True(IdentifierNormalizer.TryNormalizePackageUrl("pkg:npm/%40scope/package", out var canonical));
Assert.Equal(expectedIdentifier, canonical);
}
var method = typeof(OsvMapper).GetMethod("DetermineIdentifier", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(method);
var directIdentifier = method!.Invoke(null, new object?[] { dto.Affected![0].Package!, ecosystem }) as string;
Assert.Equal(expectedIdentifier, directIdentifier);
var (document, dtoRecord) = CreateDocumentAndDtoRecord(dto, ecosystem);
var advisory = OsvMapper.Map(dto, document, dtoRecord, ecosystem);
var affected = Assert.Single(advisory.AffectedPackages);
Assert.Equal(expectedIdentifier, affected.Identifier);
}
private static (DocumentRecord Document, DtoRecord DtoRecord) CreateDocumentAndDtoRecord(OsvVulnerabilityDto dto, string ecosystem)
{
var recordedAt = DateTimeOffset.UtcNow;
var document = new DocumentRecord(
Guid.NewGuid(),
OsvConnectorPlugin.SourceName,
$"https://osv.dev/vulnerability/{dto.Id}",
recordedAt,
"sha256",
DocumentStatuses.PendingParse,
"application/json",
null,
new Dictionary<string, string>(StringComparer.Ordinal)
{
["osv.ecosystem"] = ecosystem,
},
null,
dto.Modified,
null,
null);
var payload = new BsonDocument("id", dto.Id);
var dtoRecord = new DtoRecord(Guid.NewGuid(), document.Id, OsvConnectorPlugin.SourceName, "osv.v1", payload, recordedAt);
return (document, dtoRecord);
}
}
Assert.Single(advisory.CvssMetrics);
Assert.Equal("3.1", advisory.CvssMetrics[0].Version);
}
[Fact]
public void Map_AssignsSeverityFallbackWhenCvssVectorUnsupported()
{
using var databaseSpecificJson = JsonDocument.Parse("""
{
"severity": "MODERATE",
"cwe_ids": ["CWE-290"]
}
""");
var dto = new OsvVulnerabilityDto
{
Id = "OSV-CVSS4",
Summary = "Severity-only advisory",
Details = "OSV entry that lacks a parsable CVSS vector.",
Published = DateTimeOffset.UtcNow.AddDays(-10),
Modified = DateTimeOffset.UtcNow.AddDays(-5),
DatabaseSpecific = databaseSpecificJson.RootElement,
Severity = new[]
{
new OsvSeverityDto
{
Type = "CVSS_V4",
Score = "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"
}
}
};
var (document, dtoRecord) = CreateDocumentAndDtoRecord(dto, "PyPI");
var advisory = OsvMapper.Map(dto, document, dtoRecord, "PyPI");
Assert.True(advisory.CvssMetrics.IsEmpty);
Assert.Equal("medium", advisory.Severity);
Assert.Equal("osv:severity/medium", advisory.CanonicalMetricId);
var weakness = Assert.Single(advisory.Cwes);
var provenance = Assert.Single(weakness.Provenance);
Assert.Equal("database_specific.cwe_ids", provenance.DecisionReason);
}
[Theory]
[InlineData("Go", "github.com/example/project", "pkg:golang/github.com/example/project")]
[InlineData("PyPI", "social_auth_app_django", "pkg:pypi/social-auth-app-django")]
[InlineData("npm", "@Scope/Package", "pkg:npm/%40scope/package")]
[InlineData("Maven", "org.example:library", "pkg:maven/org.example/library")]
[InlineData("crates", "serde", "pkg:cargo/serde")]
public void Map_InfersCanonicalPackageUrlWhenPurlMissing(string ecosystem, string packageName, string expectedIdentifier)
{
var dto = new OsvVulnerabilityDto
{
Id = $"OSV-{ecosystem}-PURL",
Summary = "Test advisory",
Details = "Details",
Published = DateTimeOffset.UtcNow.AddDays(-1),
Modified = DateTimeOffset.UtcNow,
Affected = new[]
{
new OsvAffectedPackageDto
{
Package = new OsvPackageDto
{
Ecosystem = ecosystem,
Name = packageName,
Purl = null,
},
Ranges = null,
}
}
};
if (string.Equals(ecosystem, "npm", StringComparison.OrdinalIgnoreCase))
{
Assert.True(IdentifierNormalizer.TryNormalizePackageUrl("pkg:npm/%40scope/package", out var canonical));
Assert.Equal(expectedIdentifier, canonical);
}
var method = typeof(OsvMapper).GetMethod("DetermineIdentifier", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(method);
var directIdentifier = method!.Invoke(null, new object?[] { dto.Affected![0].Package!, ecosystem }) as string;
Assert.Equal(expectedIdentifier, directIdentifier);
var (document, dtoRecord) = CreateDocumentAndDtoRecord(dto, ecosystem);
var advisory = OsvMapper.Map(dto, document, dtoRecord, ecosystem);
var affected = Assert.Single(advisory.AffectedPackages);
Assert.Equal(expectedIdentifier, affected.Identifier);
}
private static (DocumentRecord Document, DtoRecord DtoRecord) CreateDocumentAndDtoRecord(OsvVulnerabilityDto dto, string ecosystem)
{
var recordedAt = DateTimeOffset.UtcNow;
var document = new DocumentRecord(
Guid.NewGuid(),
OsvConnectorPlugin.SourceName,
$"https://osv.dev/vulnerability/{dto.Id}",
recordedAt,
"sha256",
DocumentStatuses.PendingParse,
"application/json",
null,
new Dictionary<string, string>(StringComparer.Ordinal)
{
["osv.ecosystem"] = ecosystem,
},
null,
dto.Modified,
null,
null);
var payload = new BsonDocument("id", dto.Id);
var dtoRecord = new DtoRecord(Guid.NewGuid(), document.Id, OsvConnectorPlugin.SourceName, "osv.v1", payload, recordedAt);
return (document, dtoRecord);
}
}

View File

@@ -6,8 +6,8 @@ using MongoDB.Bson;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage.Mongo.Documents;
using StellaOps.Concelier.Storage.Mongo.Dtos;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Connector.Common;
using Xunit;
using Xunit.Abstractions;