Add Canonical JSON serialization library with tests and documentation

- Implemented CanonJson class for deterministic JSON serialization and hashing.
- Added unit tests for CanonJson functionality, covering various scenarios including key sorting, handling of nested objects, arrays, and special characters.
- Created project files for the Canonical JSON library and its tests, including necessary package references.
- Added README.md for library usage and API reference.
- Introduced RabbitMqIntegrationFactAttribute for conditional RabbitMQ integration tests.
This commit is contained in:
master
2025-12-19 15:35:00 +02:00
parent 43882078a4
commit 951a38d561
192 changed files with 27550 additions and 2611 deletions

View File

@@ -0,0 +1,279 @@
// -----------------------------------------------------------------------------
// VulnSurfaceIntegrationTests.cs
// Sprint: SPRINT_3700_0002_0001_vuln_surfaces_core
// Task: SURF-023
// Description: Integration tests with real CVE data (Newtonsoft.Json).
// -----------------------------------------------------------------------------
using System.Collections.Generic;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Scanner.VulnSurfaces.Builder;
using StellaOps.Scanner.VulnSurfaces.CallGraph;
using StellaOps.Scanner.VulnSurfaces.Download;
using StellaOps.Scanner.VulnSurfaces.Fingerprint;
using StellaOps.Scanner.VulnSurfaces.Triggers;
using Xunit;
namespace StellaOps.Scanner.VulnSurfaces.Tests;
/// <summary>
/// Integration tests for VulnSurfaceBuilder using real packages.
/// These tests require network access and may be slow.
/// </summary>
[Trait("Category", "Integration")]
[Trait("Category", "SlowTests")]
public sealed class VulnSurfaceIntegrationTests : IDisposable
{
private readonly string _workDir;
public VulnSurfaceIntegrationTests()
{
_workDir = Path.Combine(Path.GetTempPath(), "vuln-surface-tests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_workDir);
}
public void Dispose()
{
try
{
if (Directory.Exists(_workDir))
{
Directory.Delete(_workDir, recursive: true);
}
}
catch
{
// Ignore cleanup errors
}
}
/// <summary>
/// Tests vulnerability surface extraction for Newtonsoft.Json CVE-2024-21907.
/// This CVE relates to type confusion in TypeNameHandling.
/// Vuln: 13.0.1, Fixed: 13.0.3
/// </summary>
[Fact(Skip = "Requires network access and ~30s runtime")]
public async Task BuildAsync_NewtonsoftJson_CVE_2024_21907_DetectsSinks()
{
// Arrange
var builder = CreateBuilder();
var request = new VulnSurfaceBuildRequest
{
CveId = "CVE-2024-21907",
PackageName = "Newtonsoft.Json",
Ecosystem = "nuget",
VulnVersion = "13.0.1",
FixedVersion = "13.0.3",
WorkingDirectory = _workDir,
ExtractTriggers = true
};
// Act
var result = await builder.BuildAsync(request);
// Assert
Assert.True(result.Success, result.Error ?? "Build should succeed");
Assert.NotNull(result.Surface);
Assert.Equal("CVE-2024-21907", result.Surface.CveId);
Assert.Equal("nuget", result.Surface.Ecosystem);
// Should detect changed methods in the security fix
Assert.NotEmpty(result.Surface.Sinks);
// Log for visibility
foreach (var sink in result.Surface.Sinks)
{
System.Diagnostics.Debug.WriteLine($"Sink: {sink.MethodKey} ({sink.ChangeType})");
}
}
/// <summary>
/// Tests building a surface for a small well-known package.
/// Uses Humanizer.Core which is small and has version differences.
/// </summary>
[Fact(Skip = "Requires network access and ~15s runtime")]
public async Task BuildAsync_HumanizerCore_DetectsMethodChanges()
{
// Arrange
var builder = CreateBuilder();
var request = new VulnSurfaceBuildRequest
{
CveId = "TEST-0001",
PackageName = "Humanizer.Core",
Ecosystem = "nuget",
VulnVersion = "2.14.0",
FixedVersion = "2.14.1",
WorkingDirectory = _workDir,
ExtractTriggers = false // Skip trigger extraction for speed
};
// Act
var result = await builder.BuildAsync(request);
// Assert
Assert.True(result.Success, result.Error ?? "Build should succeed");
Assert.NotNull(result.Surface);
// Even if no sinks are found, the surface should be created successfully
Assert.NotNull(result.Surface.Sinks);
}
/// <summary>
/// Tests that invalid package name returns appropriate error.
/// </summary>
[Fact(Skip = "Requires network access")]
public async Task BuildAsync_InvalidPackage_ReturnsFailed()
{
// Arrange
var builder = CreateBuilder();
var request = new VulnSurfaceBuildRequest
{
CveId = "TEST-INVALID",
PackageName = "This.Package.Does.Not.Exist.12345",
Ecosystem = "nuget",
VulnVersion = "1.0.0",
FixedVersion = "1.0.1",
WorkingDirectory = _workDir,
ExtractTriggers = false
};
// Act
var result = await builder.BuildAsync(request);
// Assert
Assert.False(result.Success);
Assert.NotNull(result.Error);
Assert.Contains("Failed to download", result.Error);
}
/// <summary>
/// Tests that unsupported ecosystem returns error.
/// </summary>
[Fact]
public async Task BuildAsync_UnsupportedEcosystem_ReturnsFailed()
{
// Arrange
var builder = CreateBuilder();
var request = new VulnSurfaceBuildRequest
{
CveId = "TEST-UNSUPPORTED",
PackageName = "some-package",
Ecosystem = "cargo", // Not supported yet
VulnVersion = "1.0.0",
FixedVersion = "1.0.1",
WorkingDirectory = _workDir,
ExtractTriggers = false
};
// Act
var result = await builder.BuildAsync(request);
// Assert
Assert.False(result.Success);
Assert.Contains("No downloader for ecosystem", result.Error);
}
/// <summary>
/// Tests surface building with trigger extraction.
/// </summary>
[Fact(Skip = "Requires network access and ~45s runtime")]
public async Task BuildAsync_WithTriggers_ExtractsTriggerMethods()
{
// Arrange
var builder = CreateBuilder();
var request = new VulnSurfaceBuildRequest
{
CveId = "CVE-2024-21907",
PackageName = "Newtonsoft.Json",
Ecosystem = "nuget",
VulnVersion = "13.0.1",
FixedVersion = "13.0.3",
WorkingDirectory = _workDir,
ExtractTriggers = true
};
// Act
var result = await builder.BuildAsync(request);
// Assert
Assert.True(result.Success, result.Error ?? "Build should succeed");
Assert.NotNull(result.Surface);
// When trigger extraction is enabled, we should have trigger info
// Note: TriggerCount may be 0 if no public API calls into the changed methods
Assert.True(result.Surface.TriggerCount >= 0);
}
/// <summary>
/// Tests deterministic output for the same inputs.
/// </summary>
[Fact(Skip = "Requires network access and ~60s runtime")]
public async Task BuildAsync_SameInput_ProducesDeterministicOutput()
{
// Arrange
var builder = CreateBuilder();
var request = new VulnSurfaceBuildRequest
{
CveId = "CVE-2024-21907",
PackageName = "Newtonsoft.Json",
Ecosystem = "nuget",
VulnVersion = "13.0.1",
FixedVersion = "13.0.3",
WorkingDirectory = Path.Combine(_workDir, "run1"),
ExtractTriggers = false
};
// Act
var result1 = await builder.BuildAsync(request);
// Reset for second run
request = request with { WorkingDirectory = Path.Combine(_workDir, "run2") };
var result2 = await builder.BuildAsync(request);
// Assert
Assert.True(result1.Success && result2.Success);
Assert.NotNull(result1.Surface);
Assert.NotNull(result2.Surface);
// Sink count should be identical
Assert.Equal(result1.Surface.Sinks.Count, result2.Surface.Sinks.Count);
// Method keys should be identical
var keys1 = result1.Surface.Sinks.Select(s => s.MethodKey).OrderBy(k => k).ToList();
var keys2 = result2.Surface.Sinks.Select(s => s.MethodKey).OrderBy(k => k).ToList();
Assert.Equal(keys1, keys2);
}
private VulnSurfaceBuilder CreateBuilder()
{
var downloaders = new List<IPackageDownloader>
{
new NuGetPackageDownloader(
new HttpClient(),
NullLogger<NuGetPackageDownloader>.Instance,
TimeProvider.System)
};
var fingerprinters = new List<IMethodFingerprinter>
{
new CecilMethodFingerprinter(NullLogger<CecilMethodFingerprinter>.Instance)
};
var diffEngine = new MethodDiffEngine(NullLogger<MethodDiffEngine>.Instance);
var triggerExtractor = new TriggerMethodExtractor(
NullLogger<TriggerMethodExtractor>.Instance);
var graphBuilders = new List<IInternalCallGraphBuilder>
{
new CecilInternalCallGraphBuilder(NullLogger<CecilInternalCallGraphBuilder>.Instance)
};
return new VulnSurfaceBuilder(
downloaders,
fingerprinters,
diffEngine,
triggerExtractor,
graphBuilders,
NullLogger<VulnSurfaceBuilder>.Instance);
}
}