Add SBOM, symbols, traces, and VEX files for CVE-2022-21661 SQLi case
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Created CycloneDX and SPDX SBOM files for both reachable and unreachable images.
- Added symbols.json detailing function entry and sink points in the WordPress code.
- Included runtime traces for function calls in both reachable and unreachable scenarios.
- Developed OpenVEX files indicating vulnerability status and justification for both cases.
- Updated README for evaluator harness to guide integration with scanner output.
This commit is contained in:
master
2025-11-08 20:53:45 +02:00
parent 515975edc5
commit 536f6249a6
837 changed files with 37279 additions and 14675 deletions

View File

@@ -1,34 +1,35 @@
using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Cas;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.TestUtilities;
using Xunit;
namespace StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.Cas;
public sealed class LocalCasClientTests
{
[Fact]
public async Task VerifyWriteAsync_WritesProbeObject()
{
await using var temp = new TempDirectory();
var client = new LocalCasClient(new LocalCasOptions
{
RootDirectory = temp.Path,
Algorithm = "sha256"
});
var result = await client.VerifyWriteAsync(CancellationToken.None);
Assert.Equal("sha256", result.Algorithm);
Assert.True(File.Exists(result.Path));
var bytes = await File.ReadAllBytesAsync(result.Path);
Assert.Equal("stellaops-buildx-probe"u8.ToArray(), bytes);
var expectedDigest = Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
Assert.Equal(expectedDigest, result.Digest);
}
}
using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Cryptography;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Cas;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.TestUtilities;
using Xunit;
namespace StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.Cas;
public sealed class LocalCasClientTests
{
[Fact]
public async Task VerifyWriteAsync_WritesProbeObject()
{
await using var temp = new TempDirectory();
var client = new LocalCasClient(new LocalCasOptions
{
RootDirectory = temp.Path,
Algorithm = "sha256"
}, CryptoHashFactory.CreateDefault());
var result = await client.VerifyWriteAsync(CancellationToken.None);
Assert.Equal("sha256", result.Algorithm);
Assert.True(File.Exists(result.Path));
var bytes = await File.ReadAllBytesAsync(result.Path);
Assert.Equal("stellaops-buildx-probe"u8.ToArray(), bytes);
var expectedDigest = Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
Assert.Equal(expectedDigest, result.Digest);
}
}

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using StellaOps.Cryptography;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Descriptor;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Manifest;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.TestUtilities;
@@ -36,15 +37,49 @@ public sealed class DescriptorCommandSurfaceTests
var manifestOutputPath = Path.Combine(temp.Path, "out", "surface-manifest.json");
var repoRoot = TestPathHelper.FindRepositoryRoot();
var manifestDirectory = Path.Combine(repoRoot, "src", "Scanner", "StellaOps.Scanner.Sbomer.BuildXPlugin");
var pluginAssembly = typeof(BuildxPluginManifest).Assembly.Location;
var normalizedRoot = repoRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var rootFolder = Path.GetFileName(normalizedRoot);
var actualRepoRoot = string.Equals(rootFolder, "src", StringComparison.OrdinalIgnoreCase)
? Directory.GetParent(normalizedRoot)?.FullName ?? normalizedRoot
: normalizedRoot;
var sourceRoot = string.Equals(rootFolder, "src", StringComparison.OrdinalIgnoreCase)
? normalizedRoot
: Path.Combine(actualRepoRoot, "src");
var pluginProjectRoot = Path.Combine(sourceRoot, "Scanner", "StellaOps.Scanner.Sbomer.BuildXPlugin");
var manifestDirectoryCandidates = new[]
{
Path.Combine(actualRepoRoot, "plugins", "scanner", "buildx", "StellaOps.Scanner.Sbomer.BuildXPlugin"),
pluginProjectRoot
};
var manifestDirectory = manifestDirectoryCandidates.FirstOrDefault(Directory.Exists)
?? throw new DirectoryNotFoundException(
$"BuildX manifest directory not found under '{string.Join("', '", manifestDirectoryCandidates)}'.");
var testsOutputDirectory = Path.GetDirectoryName(typeof(DescriptorCommandSurfaceTests).Assembly.Location)
?? throw new InvalidOperationException("Unable to resolve test assembly directory.");
var targetFramework = new DirectoryInfo(testsOutputDirectory).Name;
var configuration = Directory.GetParent(testsOutputDirectory)?.Name ?? "Debug";
var pluginAssembly = Path.Combine(
pluginProjectRoot,
"bin",
configuration,
targetFramework,
"StellaOps.Scanner.Sbomer.BuildXPlugin.dll");
if (!File.Exists(pluginAssembly))
{
throw new FileNotFoundException($"BuildX plug-in assembly not found at '{pluginAssembly}'.");
}
var psi = new ProcessStartInfo("dotnet")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
WorkingDirectory = repoRoot
WorkingDirectory = actualRepoRoot
};
psi.ArgumentList.Add(pluginAssembly);
@@ -82,7 +117,9 @@ public sealed class DescriptorCommandSurfaceTests
var descriptor = JsonSerializer.Deserialize<DescriptorDocument>(stdout, new JsonSerializerOptions(JsonSerializerDefaults.Web));
Assert.NotNull(descriptor);
Assert.Equal("stellaops.buildx.descriptor.v1", descriptor!.Schema);
Assert.Equal("sha256:d07d06ae82e1789a5b505731f3ec3add106e23a55395213c9a881c7e816c695c", descriptor.Artifact.Digest);
var hash = CryptoHashFactory.CreateDefault();
var expectedDigest = ComputeSha256Digest(hash, sbomPath);
Assert.Equal(expectedDigest, descriptor.Artifact.Digest);
Assert.Contains("surface manifest stored", stderr, StringComparison.OrdinalIgnoreCase);
Assert.True(File.Exists(manifestOutputPath));
@@ -109,14 +146,21 @@ public sealed class DescriptorCommandSurfaceTests
throw new InvalidOperationException($"Unsupported CAS URI {casUri}.");
}
var slashIndex = casUri.IndexOf(/, prefix.Length);
var slashIndex = casUri.IndexOf('/', prefix.Length);
if (slashIndex < 0)
{
throw new InvalidOperationException($"CAS URI {casUri} does not contain a bucket path.");
}
var relative = casUri[(slashIndex + 1)..];
var localPath = Path.Combine(casRoot, relative.Replace(/, Path.DirectorySeparatorChar));
var localPath = Path.Combine(casRoot, relative.Replace('/', Path.DirectorySeparatorChar));
return localPath;
}
private static string ComputeSha256Digest(ICryptoHash hash, string filePath)
{
var bytes = File.ReadAllBytes(filePath);
var digest = hash.ComputeHash(bytes, HashAlgorithms.Sha256);
return $"sha256:{Convert.ToHexString(digest).ToLowerInvariant()}";
}
}

View File

@@ -7,7 +7,8 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Descriptor;
using StellaOps.Cryptography;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Descriptor;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.TestUtilities;
using Xunit;
@@ -23,7 +24,7 @@ public sealed class DescriptorGeneratorTests
await File.WriteAllTextAsync(sbomPath, "{\"bomFormat\":\"CycloneDX\",\"specVersion\":\"1.5\"}");
var fakeTime = new FakeTimeProvider(new DateTimeOffset(2025, 10, 18, 12, 0, 0, TimeSpan.Zero));
var generator = new DescriptorGenerator(fakeTime);
var generator = CreateGenerator(fakeTime);
var request = new DescriptorRequest
{
@@ -72,7 +73,7 @@ public sealed class DescriptorGeneratorTests
await File.WriteAllTextAsync(sbomPath, "{\"bomFormat\":\"CycloneDX\",\"specVersion\":\"1.5\"}");
var fakeTime = new FakeTimeProvider(new DateTimeOffset(2025, 10, 18, 12, 0, 0, TimeSpan.Zero));
var generator = new DescriptorGenerator(fakeTime);
var generator = CreateGenerator(fakeTime);
var request = new DescriptorRequest
{
@@ -109,7 +110,7 @@ public sealed class DescriptorGeneratorTests
await File.WriteAllTextAsync(sbomPath, "{\"bomFormat\":\"CycloneDX\",\"specVersion\":\"1.5\"}");
var fakeTime = new FakeTimeProvider(new DateTimeOffset(2025, 10, 18, 12, 0, 0, TimeSpan.Zero));
var generator = new DescriptorGenerator(fakeTime);
var generator = CreateGenerator(fakeTime);
var baseline = new DescriptorRequest
{
@@ -133,7 +134,10 @@ public sealed class DescriptorGeneratorTests
Assert.NotEqual(baselineDocument.Provenance.ExpectedDsseSha256, variantDocument.Provenance.ExpectedDsseSha256);
}
private static string ComputeSha256File(string path)
private static DescriptorGenerator CreateGenerator(TimeProvider timeProvider)
=> new(timeProvider, CryptoHashFactory.CreateDefault());
private static string ComputeSha256File(string path)
{
using var stream = File.OpenRead(path);
var hash = SHA256.HashData(stream);

View File

@@ -7,8 +7,9 @@ using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Descriptor;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Cryptography;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Descriptor;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.TestUtilities;
using Xunit;
@@ -48,7 +49,7 @@ public sealed class DescriptorGoldenTests
}.Validate();
var fakeTime = new FakeTimeProvider(new DateTimeOffset(2025, 10, 18, 12, 0, 0, TimeSpan.Zero));
var generator = new DescriptorGenerator(fakeTime);
var generator = CreateGenerator(fakeTime);
var document = await generator.CreateAsync(request, CancellationToken.None);
var actualJson = JsonSerializer.Serialize(document, SerializerOptions);
var normalizedJson = NormalizeDescriptorJson(actualJson, Path.GetFileName(sbomPath));
@@ -129,4 +130,6 @@ public sealed class DescriptorGoldenTests
break;
}
}
}
private static DescriptorGenerator CreateGenerator(TimeProvider timeProvider)
=> new(timeProvider, CryptoHashFactory.CreateDefault());
}

View File

@@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Cryptography;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Surface;
using StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.TestUtilities;
using Xunit;
@@ -42,7 +43,7 @@ public sealed class SurfaceManifestWriterTests
EntryTraceNdjsonPath: ndjsonPath,
ManifestOutputPath: manifestOutputPath);
var writer = new SurfaceManifestWriter(TimeProvider.System);
var writer = new SurfaceManifestWriter(TimeProvider.System, CryptoHashFactory.CreateDefault());
var result = await writer.WriteAsync(options, CancellationToken.None);
Assert.NotNull(result);
@@ -88,7 +89,7 @@ public sealed class SurfaceManifestWriterTests
EntryTraceNdjsonPath: null,
ManifestOutputPath: null);
var writer = new SurfaceManifestWriter(TimeProvider.System);
var writer = new SurfaceManifestWriter(TimeProvider.System, CryptoHashFactory.CreateDefault());
var result = await writer.WriteAsync(options, CancellationToken.None);
Assert.Null(result);
}

View File

@@ -22,7 +22,8 @@ using StellaOps.Scanner.Surface.Secrets;
using StellaOps.Scanner.Surface.Validation;
using StellaOps.Scanner.Worker.Diagnostics;
using StellaOps.Scanner.Worker.Processing;
using Xunit;
using StellaOps.Scanner.Worker.Tests.TestInfrastructure;
using Xunit;
using WorkerOptions = StellaOps.Scanner.Worker.Options.ScannerWorkerOptions;
namespace StellaOps.Scanner.Worker.Tests;
@@ -108,7 +109,8 @@ public sealed class CompositeScanAnalyzerDispatcherTests
languageCatalog,
options,
loggerFactory.CreateLogger<CompositeScanAnalyzerDispatcher>(),
metrics);
metrics,
new TestCryptoHash());
var lease = new TestJobLease(metadata);
var context = new ScanJobContext(lease, TimeProvider.System, TimeProvider.System.GetUtcNow(), CancellationToken.None);

View File

@@ -19,6 +19,7 @@ using StellaOps.Scanner.Surface.Secrets;
using StellaOps.Scanner.Surface.Validation;
using StellaOps.Scanner.Worker.Options;
using StellaOps.Scanner.Worker.Processing;
using StellaOps.Scanner.Worker.Tests.TestInfrastructure;
using Xunit;
namespace StellaOps.Scanner.Worker.Tests;
@@ -166,7 +167,8 @@ public sealed class EntryTraceExecutionServiceTests : IDisposable
ISurfaceCache? surfaceCache = null,
ISurfaceValidatorRunner? surfaceValidator = null,
ISurfaceSecretProvider? surfaceSecrets = null,
ISurfaceEnvironment? surfaceEnvironment = null)
ISurfaceEnvironment? surfaceEnvironment = null,
ICryptoHash? hash = null)
{
var workerOptions = new ScannerWorkerOptions();
var entryTraceOptions = new EntryTraceAnalyzerOptions();
@@ -176,6 +178,7 @@ public sealed class EntryTraceExecutionServiceTests : IDisposable
surfaceCache ??= new InMemorySurfaceCache();
surfaceValidator ??= new NoopSurfaceValidatorRunner();
surfaceSecrets ??= new StubSurfaceSecretProvider();
hash ??= new TestCryptoHash();
var serviceProvider = new ServiceCollection()
.AddSingleton<ISurfaceEnvironment>(surfaceEnvironment)
.BuildServiceProvider();
@@ -192,7 +195,8 @@ public sealed class EntryTraceExecutionServiceTests : IDisposable
surfaceEnvironment,
surfaceCache,
surfaceSecrets,
serviceProvider);
serviceProvider,
hash);
}
private static ScanJobContext CreateContext(IReadOnlyDictionary<string, string> metadata)

View File

@@ -7,7 +7,6 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Security.Cryptography;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Scanner.Core.Contracts;
using StellaOps.Scanner.EntryTrace;
@@ -18,6 +17,7 @@ using StellaOps.Scanner.Worker.Processing;
using StellaOps.Scanner.Worker.Processing.Surface;
using StellaOps.Scanner.Worker.Tests.TestInfrastructure;
using Xunit;
using StellaOps.Cryptography;
namespace StellaOps.Scanner.Worker.Tests;
@@ -34,12 +34,14 @@ public sealed class SurfaceManifestStageExecutorTests
using var listener = new WorkerMeterListener();
listener.Start();
var hash = new DefaultCryptoHash();
var executor = new SurfaceManifestStageExecutor(
publisher,
cache,
environment,
metrics,
NullLogger<SurfaceManifestStageExecutor>.Instance);
NullLogger<SurfaceManifestStageExecutor>.Instance,
hash);
var context = CreateContext();
@@ -68,12 +70,14 @@ public sealed class SurfaceManifestStageExecutorTests
using var listener = new WorkerMeterListener();
listener.Start();
var hash = new DefaultCryptoHash();
var executor = new SurfaceManifestStageExecutor(
publisher,
cache,
environment,
metrics,
NullLogger<SurfaceManifestStageExecutor>.Instance);
NullLogger<SurfaceManifestStageExecutor>.Instance,
hash);
var context = CreateContext();
PopulateAnalysis(context);

View File

@@ -0,0 +1,47 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Cryptography;
namespace StellaOps.Scanner.Worker.Tests.TestInfrastructure;
internal sealed class TestCryptoHash : ICryptoHash
{
public byte[] ComputeHash(ReadOnlySpan<byte> data, string? algorithmId = null)
{
using var algorithm = CreateAlgorithm(algorithmId);
return algorithm.ComputeHash(data.ToArray());
}
public string ComputeHashHex(ReadOnlySpan<byte> data, string? algorithmId = null)
=> Convert.ToHexString(ComputeHash(data, algorithmId)).ToLowerInvariant();
public string ComputeHashBase64(ReadOnlySpan<byte> data, string? algorithmId = null)
=> Convert.ToBase64String(ComputeHash(data, algorithmId));
public async ValueTask<byte[]> ComputeHashAsync(Stream stream, string? algorithmId = null, CancellationToken cancellationToken = default)
{
using var algorithm = CreateAlgorithm(algorithmId);
await using var buffer = new MemoryStream();
await stream.CopyToAsync(buffer, cancellationToken).ConfigureAwait(false);
return algorithm.ComputeHash(buffer.ToArray());
}
public async ValueTask<string> ComputeHashHexAsync(Stream stream, string? algorithmId = null, CancellationToken cancellationToken = default)
{
var bytes = await ComputeHashAsync(stream, algorithmId, cancellationToken).ConfigureAwait(false);
return Convert.ToHexString(bytes).ToLowerInvariant();
}
private static HashAlgorithm CreateAlgorithm(string? algorithmId)
{
return algorithmId?.ToUpperInvariant() switch
{
null or "" or HashAlgorithms.Sha256 => SHA256.Create(),
HashAlgorithms.Sha512 => SHA512.Create(),
_ => throw new NotSupportedException($"Test crypto hash does not support algorithm {algorithmId}.")
};
}
}