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

@@ -0,0 +1,182 @@
using System.Linq;
using System.Text.Json.Nodes;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Findings.Ledger.Domain;
using StellaOps.Findings.Ledger.Infrastructure;
using StellaOps.Findings.Ledger.Infrastructure.InMemory;
using StellaOps.Findings.Ledger.Options;
using StellaOps.Findings.Ledger.Services;
using StellaOps.Findings.Ledger.Workflow;
using Xunit;
namespace StellaOps.Findings.Ledger.Tests;
public sealed class FindingWorkflowServiceTests
{
private readonly InMemoryLedgerEventRepository _repository = new();
private readonly IFindingWorkflowService _service;
private readonly FakeTimeProvider _timeProvider = new(DateTimeOffset.Parse("2025-11-07T18:30:15Z"));
public FindingWorkflowServiceTests()
{
var writeService = new LedgerEventWriteService(
_repository,
new NoOpMerkleScheduler(),
NullLogger<LedgerEventWriteService>.Instance);
var options = Microsoft.Extensions.Options.Options.Create(new LedgerServiceOptions
{
Attachments = new LedgerServiceOptions.AttachmentsOptions
{
Enabled = true,
EncryptionKey = Convert.ToBase64String(Enumerable.Repeat((byte)0x22, 32).ToArray()),
SignedUrlBase = "https://evidence.local/attachments",
SignedUrlSecret = "signed-secret",
SignedUrlLifetime = TimeSpan.FromMinutes(5),
RequireConsoleCsrf = false
}
});
_service = new FindingWorkflowService(
_repository,
writeService,
new FakeAttachmentEncryptionService(),
new FakeAttachmentUrlSigner(),
options,
_timeProvider,
NullLogger<FindingWorkflowService>.Instance);
}
[Fact]
public async Task AssignAsync_WritesLedgerEventWithAssigneeAndAttachments()
{
var request = new AssignWorkflowRequest
{
TenantId = "tenant-east",
PolicyVersion = "sha256:policy@1",
FindingId = "finding|sha256:abc|CVE-2025-1234",
ArtifactId = "sha256:abc",
VulnerabilityId = "CVE-2025-1234",
Actor = new WorkflowActor("user:alice", "operator", "Alice"),
Assignee = new WorkflowAssignee("user:bob", "operator", "Bob"),
Comment = "Taking ownership",
Attachments = new[]
{
new WorkflowAttachmentMetadata(
"att-1",
"evidence.txt",
"text/plain",
128,
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
new Dictionary<string, string> { ["scope"] = "triage" })
}
};
var result = await _service.AssignAsync(request, CancellationToken.None);
result.Status.Should().Be(LedgerWriteStatus.Success);
result.Record.Should().NotBeNull();
var record = result.Record!;
record.EventType.Should().Be(LedgerEventConstants.EventFindingAssignmentChanged);
record.SequenceNumber.Should().Be(1);
record.ChainId.Should().NotBe(Guid.Empty);
record.OccurredAt.Should().Be(_timeProvider.UtcNow);
var payload = ExtractPayload(record);
payload["action"]!.GetValue<string>().Should().Be("assign");
payload["assignee"]!.AsObject()["id"]!.GetValue<string>().Should().Be("user:bob");
var attachments = payload["attachments"]!.AsArray();
attachments.Count.Should().Be(1);
var attachmentNode = attachments[0]!.AsObject();
attachmentNode["fileName"]!.GetValue<string>().Should().Be("evidence.txt");
attachmentNode["signedUrl"]!.GetValue<string>().Should().StartWith("https://signed.local/");
attachmentNode["envelope"]!.AsObject()["algorithm"]!.GetValue<string>().Should().Be("AES-256-GCM");
}
[Fact]
public async Task AcceptRiskAsync_DefaultsStatusAndPersistsJustification()
{
var request = new AcceptRiskWorkflowRequest
{
TenantId = "tenant-east",
PolicyVersion = "sha256:policy@1",
FindingId = "finding|sha256:abc|CVE-2025-1234",
ArtifactId = "sha256:abc",
VulnerabilityId = "CVE-2025-1234",
Actor = new WorkflowActor("user:alice", "operator"),
Justification = "Risk accepted for 30 days",
ExpiresAt = _timeProvider.UtcNow.AddDays(30),
RiskOwner = "ops-team"
};
var result = await _service.AcceptRiskAsync(request, CancellationToken.None);
result.Status.Should().Be(LedgerWriteStatus.Success);
var payload = ExtractPayload(result.Record!);
payload["status"]!.GetValue<string>().Should().Be("accepted_risk");
payload["justification"]!.GetValue<string>().Should().Be("Risk accepted for 30 days");
payload["riskOwner"]!.GetValue<string>().Should().Be("ops-team");
payload.TryGetPropertyValue("attachments", out _).Should().BeFalse();
}
[Fact]
public async Task CommentAsync_ValidatesCommentPresence()
{
var request = new CommentWorkflowRequest
{
TenantId = "tenant-east",
PolicyVersion = "sha256:policy@1",
FindingId = "finding|sha256:abc|CVE-2025-1234",
ArtifactId = "sha256:abc",
VulnerabilityId = "CVE-2025-1234",
Actor = new WorkflowActor("user:alice", "operator"),
Comment = " "
};
var result = await _service.CommentAsync(request, CancellationToken.None);
result.Status.Should().Be(LedgerWriteStatus.ValidationFailed);
result.Errors.Should().Contain("comment_required");
}
private static JsonObject ExtractPayload(LedgerEventRecord record)
{
var eventNode = record.EventBody["event"]?.AsObject()
?? throw new InvalidOperationException("event node missing");
return eventNode["payload"]?.AsObject()
?? throw new InvalidOperationException("payload node missing");
}
private sealed class NoOpMerkleScheduler : IMerkleAnchorScheduler
{
public Task EnqueueAsync(LedgerEventRecord record, CancellationToken cancellationToken) => Task.CompletedTask;
}
private sealed class FakeTimeProvider : TimeProvider
{
public FakeTimeProvider(DateTimeOffset fixedTime) => UtcNow = fixedTime;
public DateTimeOffset UtcNow { get; }
public override DateTimeOffset GetUtcNow() => UtcNow;
}
private sealed class FakeAttachmentEncryptionService : IAttachmentEncryptionService
{
public AttachmentEncryptionResult CreateEnvelope(NormalizedAttachment attachment, DateTimeOffset now)
=> new(
Algorithm: "AES-256-GCM",
Ciphertext: Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("ciphertext")),
Nonce: Convert.ToBase64String(new byte[] { 1, 2, 3 }),
Tag: Convert.ToBase64String(new byte[] { 4, 5, 6 }),
ExpiresAt: now.AddMinutes(5));
}
private sealed class FakeAttachmentUrlSigner : IAttachmentUrlSigner
{
public AttachmentSignedUrl Sign(string attachmentId, DateTimeOffset now, TimeSpan lifetime)
=> new(new Uri($"https://signed.local/{attachmentId}?sig=fake"), now.Add(lifetime));
}
}