Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.VulnSurfaces.Tests/VulnSurfaceIntegrationTests.cs
2026-01-24 00:12:43 +02:00

324 lines
10 KiB
C#

// -----------------------------------------------------------------------------
// 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.
/// Set STELLA_NETWORK_TESTS=1 to enable these tests.
/// </summary>
[Trait("Category", "Integration")]
[Trait("Category", "SlowTests")]
[Trait("Category", "NetworkTests")]
public sealed class VulnSurfaceIntegrationTests : IDisposable
{
private readonly string _workDir;
private static readonly bool NetworkTestsEnabled =
Environment.GetEnvironmentVariable("STELLA_NETWORK_TESTS") == "1" ||
Environment.GetEnvironmentVariable("CI") == "true";
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
}
}
private void SkipIfNoNetwork()
{
if (!NetworkTestsEnabled)
{
Assert.True(true, "Network tests disabled. Set STELLA_NETWORK_TESTS=1 to enable.");
return;
}
}
/// <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]
public async Task BuildAsync_NewtonsoftJson_CVE_2024_21907_DetectsSinks()
{
if (!NetworkTestsEnabled)
{
Assert.True(true, "Network tests disabled");
return;
}
// 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]
public async Task BuildAsync_HumanizerCore_DetectsMethodChanges()
{
if (!NetworkTestsEnabled)
{
Assert.True(true, "Network tests disabled");
return;
}
// 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]
public async Task BuildAsync_InvalidPackage_ReturnsFailed()
{
if (!NetworkTestsEnabled)
{
Assert.True(true, "Network tests disabled");
return;
}
// 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]
public async Task BuildAsync_WithTriggers_ExtractsTriggerMethods()
{
if (!NetworkTestsEnabled)
{
Assert.True(true, "Network tests disabled");
return;
}
// 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]
public async Task BuildAsync_SameInput_ProducesDeterministicOutput()
{
if (!NetworkTestsEnabled)
{
Assert.True(true, "Network tests disabled");
return;
}
// 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,
Microsoft.Extensions.Options.Options.Create(new NuGetDownloaderOptions()))
};
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 CecilInternalGraphBuilder(NullLogger<CecilInternalGraphBuilder>.Instance)
};
return new VulnSurfaceBuilder(
downloaders,
fingerprinters,
diffEngine,
triggerExtractor,
graphBuilders,
NullLogger<VulnSurfaceBuilder>.Instance);
}
}