save checkpoint
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Analyzers.OS;
|
||||
using StellaOps.Scanner.Analyzers.OS.Dpkg;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Tests.Dpkg;
|
||||
|
||||
public sealed class DpkgChangelogBugCorrelationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsBugReferencesAndCveMappingsFromDpkgChangelog()
|
||||
{
|
||||
var rootPath = Path.Combine(Path.GetTempPath(), $"stellaops-dpkg-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(rootPath);
|
||||
|
||||
try
|
||||
{
|
||||
WriteFixture(rootPath);
|
||||
|
||||
var analyzer = new DpkgPackageAnalyzer(NullLogger<DpkgPackageAnalyzer>.Instance);
|
||||
var metadata = new Dictionary<string, string> { [ScanMetadataKeys.RootFilesystemPath] = rootPath };
|
||||
var context = new OSPackageAnalyzerContext(
|
||||
rootPath,
|
||||
workspacePath: null,
|
||||
TimeProvider.System,
|
||||
NullLoggerFactory.Instance.CreateLogger("dpkg-changelog-tests"),
|
||||
metadata);
|
||||
|
||||
var result = await analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
||||
var package = Assert.Single(result.Packages);
|
||||
|
||||
Assert.Equal("debian:#123456,launchpad:#7654321,rhbz:#424242", package.VendorMetadata["changelogBugRefs"]);
|
||||
Assert.Equal(
|
||||
"debian:#123456=>CVE-2026-1000;launchpad:#7654321=>CVE-2026-1000;rhbz:#424242=>CVE-2026-1001",
|
||||
package.VendorMetadata["changelogBugToCves"]);
|
||||
Assert.Contains("CVE-2026-1000", package.CveHints);
|
||||
Assert.Contains("CVE-2026-1001", package.CveHints);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(rootPath))
|
||||
{
|
||||
Directory.Delete(rootPath, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteFixture(string rootPath)
|
||||
{
|
||||
var statusPath = Path.Combine(rootPath, "var", "lib", "dpkg", "status");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(statusPath)!);
|
||||
File.WriteAllText(
|
||||
statusPath,
|
||||
"""
|
||||
Package: bash
|
||||
Status: install ok installed
|
||||
Priority: important
|
||||
Section: shells
|
||||
Installed-Size: 1024
|
||||
Maintainer: Debian Developers <debian-devel@lists.debian.org>
|
||||
Architecture: amd64
|
||||
Version: 5.2.21-2
|
||||
Description: GNU Bourne Again SHell
|
||||
|
||||
""");
|
||||
|
||||
var listPath = Path.Combine(rootPath, "var", "lib", "dpkg", "info", "bash.list");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(listPath)!);
|
||||
File.WriteAllText(
|
||||
listPath,
|
||||
"""
|
||||
/usr/share/doc/bash/changelog.Debian.gz
|
||||
""");
|
||||
|
||||
var changelogPath = Path.Combine(rootPath, "usr", "share", "doc", "bash", "changelog.Debian.gz");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(changelogPath)!);
|
||||
|
||||
using var fs = File.Create(changelogPath);
|
||||
using var gzip = new GZipStream(fs, CompressionMode.Compress);
|
||||
using var writer = new StreamWriter(gzip, Encoding.UTF8);
|
||||
writer.Write(
|
||||
"""
|
||||
bash (5.2.21-2) unstable; urgency=medium
|
||||
|
||||
* Backport parser fix (Closes: #123456; LP: #7654321; CVE-2026-1000).
|
||||
* Hardening update (RHBZ#424242; CVE-2026-1001).
|
||||
|
||||
-- Debian Maintainer <maintainer@example.org> Thu, 12 Feb 2026 09:00:00 +0000
|
||||
""");
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,11 @@
|
||||
"release": "8.el9",
|
||||
"sourcePackage": "openssl-3.2.1-8.el9.src.rpm",
|
||||
"license": "OpenSSL",
|
||||
"evidenceSource": "RpmDatabase",
|
||||
"cveHints": [
|
||||
"CVE-2025-1234"
|
||||
],
|
||||
"evidenceSource": "RpmDatabase",
|
||||
"cveHints": [
|
||||
"CVE-2025-1234",
|
||||
"CVE-2025-9999"
|
||||
],
|
||||
"provides": [
|
||||
"libcrypto.so.3()(64bit)",
|
||||
"openssl-libs"
|
||||
@@ -48,10 +49,12 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"vendorMetadata": {
|
||||
"buildTime": null,
|
||||
"description": null,
|
||||
"installTime": null,
|
||||
"vendorMetadata": {
|
||||
"buildTime": null,
|
||||
"changelogBugRefs": "debian:#102030,debian:#102031,launchpad:#456789,rhbz:#220001",
|
||||
"changelogBugToCves": "debian:#102030=\u003ECVE-2025-9999;debian:#102031=\u003ECVE-2025-9999;launchpad:#456789=\u003ECVE-2025-9999;rhbz:#220001=\u003ECVE-2025-1234",
|
||||
"description": null,
|
||||
"installTime": null,
|
||||
"rpm:summary": "TLS toolkit",
|
||||
"sourceRpm": "openssl-3.2.1-8.el9.src.rpm",
|
||||
"summary": "TLS toolkit",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using StellaOps.Scanner.Analyzers.OS.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Tests.Helpers;
|
||||
|
||||
public sealed class ChangelogBugReferenceExtractorTests
|
||||
{
|
||||
[Fact]
|
||||
public void Extract_MapsDebianRhbzAndLaunchpadReferencesToCves()
|
||||
{
|
||||
var result = ChangelogBugReferenceExtractor.Extract(
|
||||
"Backport parser fix. Closes: #123456, #123457; CVE-2026-0001",
|
||||
"Fix OpenSSL issue RHBZ#998877 with CVE-2026-0002.",
|
||||
"Ubuntu patch for LP: #445566 and CVE-2026-0003.");
|
||||
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
"debian:#123456",
|
||||
"debian:#123457",
|
||||
"launchpad:#445566",
|
||||
"rhbz:#998877"
|
||||
},
|
||||
result.BugReferences);
|
||||
|
||||
Assert.Equal(
|
||||
"debian:#123456=>CVE-2026-0001;debian:#123457=>CVE-2026-0001;launchpad:#445566=>CVE-2026-0003;rhbz:#998877=>CVE-2026-0002",
|
||||
result.ToBugToCvesMetadataValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Extract_DoesNotEmitMappingsWithoutCveInSameEntry()
|
||||
{
|
||||
var result = ChangelogBugReferenceExtractor.Extract(
|
||||
"Closes: #222222; housekeeping only",
|
||||
"LP: #777777 without security identifier");
|
||||
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
"debian:#222222",
|
||||
"launchpad:#777777"
|
||||
},
|
||||
result.BugReferences);
|
||||
Assert.Empty(result.BugToCves);
|
||||
Assert.Equal(string.Empty, result.ToBugToCvesMetadataValue());
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,11 @@ public sealed class OsAnalyzerDeterminismTests
|
||||
new RpmFileEntry("/usr/lib64/libcrypto.so.3", false, new Dictionary<string, string> { ["sha256"] = "abc123" }),
|
||||
new RpmFileEntry("/etc/pki/tls/openssl.cnf", true, new Dictionary<string, string> { ["md5"] = "c0ffee" })
|
||||
},
|
||||
changeLogs: new[] { "Resolves: CVE-2025-1234" },
|
||||
changeLogs: new[]
|
||||
{
|
||||
"Resolves: CVE-2025-1234 (RHBZ#220001)",
|
||||
"Fix startup regression. Closes: #102030, #102031; LP: #456789; CVE-2025-9999"
|
||||
},
|
||||
metadata: new Dictionary<string, string?> { ["summary"] = "TLS toolkit" })
|
||||
};
|
||||
|
||||
|
||||
@@ -4,5 +4,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| QA-SCANNER-VERIFY-010 | DONE | Added behavioral coverage for changelog bug-reference extraction and bug-to-CVE mapping evidence in OS analyzer outputs. |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.OS.Tests/StellaOps.Scanner.Analyzers.OS.Tests.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.CallGraph.Bun;
|
||||
using StellaOps.Scanner.Contracts;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public sealed class BunCallGraphExtractorTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_FromSource_DetectsBunEntrypointsAndSinks()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "stella-bun-callgraph-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
|
||||
try
|
||||
{
|
||||
var sourcePath = Path.Combine(root, "app.ts");
|
||||
await File.WriteAllTextAsync(
|
||||
sourcePath,
|
||||
"""
|
||||
import { Elysia } from "elysia";
|
||||
import { Hono } from "hono";
|
||||
|
||||
const app = new Elysia().get("/health", () => "ok");
|
||||
const hono = new Hono();
|
||||
hono.get("/v1/ping", (c) => c.text("pong"));
|
||||
|
||||
Bun.serve({
|
||||
fetch(req) {
|
||||
return new Response("ok");
|
||||
}
|
||||
});
|
||||
|
||||
const config = Bun.file("/tmp/config.json");
|
||||
Bun.spawn(["sh", "-c", "echo hello"]);
|
||||
""");
|
||||
|
||||
var extractor = new BunCallGraphExtractor(NullLogger<BunCallGraphExtractor>.Instance);
|
||||
var snapshot = await extractor.ExtractAsync(new CallGraphExtractionRequest("scan-bun-001", "bun", root));
|
||||
|
||||
Assert.Equal("bun", snapshot.Language);
|
||||
Assert.StartsWith("sha256:", snapshot.GraphDigest, StringComparison.Ordinal);
|
||||
|
||||
Assert.Contains(snapshot.Nodes, n => n.IsEntrypoint && n.Symbol == "Bun.serve");
|
||||
Assert.Contains(snapshot.Nodes, n => n.IsSink && n.SinkCategory == SinkCategory.FileWrite && n.Symbol == "Bun.file");
|
||||
Assert.Contains(snapshot.Nodes, n => n.IsSink && n.SinkCategory == SinkCategory.CmdExec && n.Symbol == "Bun.spawn");
|
||||
Assert.NotEmpty(snapshot.EntrypointIds);
|
||||
Assert.NotEmpty(snapshot.SinkIds);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(root))
|
||||
{
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_WithMismatchedLanguage_ThrowsArgumentException()
|
||||
{
|
||||
var extractor = new BunCallGraphExtractor(NullLogger<BunCallGraphExtractor>.Instance);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
extractor.ExtractAsync(new CallGraphExtractionRequest("scan-bun-002", "node", ".")));
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| QA-SCANNER-VERIFY-008 | DONE | `SPRINT_20260212_002_Scanner_unchecked_feature_verification_batch1.md`: added/verified binary namespace coverage for entry-trace binary intelligence in run-002 Tier 1/Tier 2 checks (2026-02-12). |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Scanner/__Tests/StellaOps.Scanner.EntryTrace.Tests/StellaOps.Scanner.EntryTrace.Tests.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
|
||||
@@ -93,7 +93,28 @@ public sealed class EntryTraceResultStoreTests
|
||||
ImmutableArray<EntryTraceEdge>.Empty,
|
||||
ImmutableArray<EntryTraceDiagnostic>.Empty,
|
||||
ImmutableArray.Create(plan),
|
||||
ImmutableArray.Create(terminal));
|
||||
ImmutableArray.Create(terminal),
|
||||
new EntryTraceBinaryIntelligence(
|
||||
ImmutableArray.Create(new EntryTraceBinaryTarget(
|
||||
"/app/main.py",
|
||||
"sha256:bin",
|
||||
"Unknown",
|
||||
"Raw",
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
ImmutableArray.Create(new EntryTraceBinaryVulnerability(
|
||||
"CVE-2024-5678",
|
||||
"main",
|
||||
"pkg:generic/demo",
|
||||
"main",
|
||||
0.91f,
|
||||
"High")))),
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
generatedAt));
|
||||
|
||||
var ndjson = EntryTraceNdjsonWriter.Serialize(
|
||||
graph,
|
||||
@@ -112,6 +133,9 @@ public sealed class EntryTraceResultStoreTests
|
||||
Assert.Equal(
|
||||
EntryTraceGraphSerializer.Serialize(result.Graph),
|
||||
EntryTraceGraphSerializer.Serialize(stored.Graph));
|
||||
Assert.NotNull(stored.Graph.BinaryIntelligence);
|
||||
Assert.Equal(1, stored.Graph.BinaryIntelligence!.TotalTargets);
|
||||
Assert.Equal(1, stored.Graph.BinaryIntelligence.TotalVulnerableMatches);
|
||||
Assert.Equal(result.Ndjson.ToArray(), stored.Ndjson.ToArray());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| QA-SCANNER-VERIFY-008 | DONE | `SPRINT_20260212_002_Scanner_unchecked_feature_verification_batch1.md`: added entry-trace store round-trip coverage for binary intelligence payload; validated in run-002 Tier 2 (2026-02-12). |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Scanner/__Tests/StellaOps.Scanner.Storage.Tests/StellaOps.Scanner.Storage.Tests.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
| HOT-002 | DONE | `SPRINT_20260210_001_DOCS_sbom_attestation_hot_lookup_contract.md`: migration coverage for `scanner.artifact_boms` partition/index profile. |
|
||||
|
||||
@@ -1,83 +1,166 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using StellaOps.Scanner.Storage.ObjectStore;
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.WebService.Tests;
|
||||
|
||||
public sealed class SbomUploadEndpointsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Upload_validates_cyclonedx_format()
|
||||
public async Task Upload_accepts_cyclonedx_inline_and_persists_record()
|
||||
{
|
||||
// This test validates that CycloneDX format detection works
|
||||
// Full integration with upload service is tested separately
|
||||
using var secrets = new TestSurfaceSecretsScope();
|
||||
await using var factory = await CreateFactoryAsync();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var sampleCycloneDx = """
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"version": 1,
|
||||
"metadata": { "timestamp": "2025-01-15T10:00:00Z" },
|
||||
"components": []
|
||||
}
|
||||
""";
|
||||
var base64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(sampleCycloneDx));
|
||||
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"version": 1,
|
||||
"components": [
|
||||
{
|
||||
"type": "library",
|
||||
"name": "left-pad",
|
||||
"version": "1.3.0",
|
||||
"purl": "pkg:npm/left-pad@1.3.0",
|
||||
"licenses": [{ "license": { "id": "MIT" } }]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"name": "chalk",
|
||||
"version": "5.0.0",
|
||||
"purl": "pkg:npm/chalk@5.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
var request = new SbomUploadRequestDto
|
||||
{
|
||||
ArtifactRef = "example.com/app:1.0",
|
||||
SbomBase64 = base64,
|
||||
ArtifactRef = "example.com/app:1.0.0",
|
||||
ArtifactDigest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
Sbom = JsonDocument.Parse(sampleCycloneDx).RootElement.Clone(),
|
||||
Source = new SbomUploadSourceDto
|
||||
{
|
||||
Tool = "syft",
|
||||
Version = "1.0.0"
|
||||
Version = "1.0.0",
|
||||
CiContext = new SbomUploadCiContextDto
|
||||
{
|
||||
BuildId = "build-42",
|
||||
Repository = "example/repo"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Verify the request is valid and can be serialized
|
||||
Assert.NotNull(request.ArtifactRef);
|
||||
Assert.NotEmpty(request.SbomBase64);
|
||||
Assert.NotNull(request.Source);
|
||||
Assert.Equal("syft", request.Source.Tool);
|
||||
var uploadResponse = await client.PostAsJsonAsync("/api/v1/sbom/upload", request);
|
||||
Assert.Equal(HttpStatusCode.Accepted, uploadResponse.StatusCode);
|
||||
|
||||
var payload = await uploadResponse.Content.ReadFromJsonAsync<SbomUploadResponseDto>();
|
||||
Assert.NotNull(payload);
|
||||
Assert.False(string.IsNullOrWhiteSpace(payload!.SbomId));
|
||||
Assert.Equal("cyclonedx", payload.Format);
|
||||
Assert.Equal("1.6", payload.FormatVersion);
|
||||
Assert.True(payload.ValidationResult.Valid);
|
||||
Assert.Equal(2, payload.ValidationResult.ComponentCount);
|
||||
Assert.Equal(0.85d, payload.ValidationResult.QualityScore);
|
||||
Assert.StartsWith("sha256:", payload.Digest, StringComparison.Ordinal);
|
||||
Assert.False(string.IsNullOrWhiteSpace(payload.AnalysisJobId));
|
||||
|
||||
var getResponse = await client.GetAsync($"/api/v1/sbom/uploads/{payload.SbomId}");
|
||||
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
|
||||
|
||||
var record = await getResponse.Content.ReadFromJsonAsync<SbomUploadRecordDto>();
|
||||
Assert.NotNull(record);
|
||||
Assert.Equal(payload.SbomId, record!.SbomId);
|
||||
Assert.Equal("example.com/app:1.0.0", record.ArtifactRef);
|
||||
Assert.Equal("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", record.ArtifactDigest);
|
||||
Assert.Equal("cyclonedx", record.Format);
|
||||
Assert.Equal("1.6", record.FormatVersion);
|
||||
Assert.Equal("build-42", record.Source?.CiContext?.BuildId);
|
||||
Assert.Equal("example/repo", record.Source?.CiContext?.Repository);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Upload_validates_spdx_format()
|
||||
public async Task Upload_accepts_spdx_base64_and_tracks_ci_context()
|
||||
{
|
||||
// This test validates that SPDX format detection works
|
||||
using var secrets = new TestSurfaceSecretsScope();
|
||||
await using var factory = await CreateFactoryAsync();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var sampleSpdx = """
|
||||
{
|
||||
"spdxVersion": "SPDX-2.3",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "test-sbom",
|
||||
"documentNamespace": "https://example.com/test",
|
||||
"packages": []
|
||||
}
|
||||
""";
|
||||
var base64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(sampleSpdx));
|
||||
{
|
||||
"spdxVersion": "SPDX-2.3",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "test-sbom",
|
||||
"documentNamespace": "https://example.com/test",
|
||||
"packages": [
|
||||
{
|
||||
"name": "openssl",
|
||||
"versionInfo": "3.0.0",
|
||||
"licenseDeclared": "Apache-2.0",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:generic/openssl@3.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
var request = new SbomUploadRequestDto
|
||||
{
|
||||
ArtifactRef = "example.com/service:2.0",
|
||||
SbomBase64 = base64
|
||||
ArtifactRef = "example.com/service:2.0.0",
|
||||
SbomBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(sampleSpdx)),
|
||||
Source = new SbomUploadSourceDto
|
||||
{
|
||||
Tool = "trivy",
|
||||
Version = "0.50.0",
|
||||
CiContext = new SbomUploadCiContextDto
|
||||
{
|
||||
BuildId = "build-77",
|
||||
Repository = "example/service-repo"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Verify the request is valid
|
||||
Assert.NotNull(request.ArtifactRef);
|
||||
Assert.NotEmpty(request.SbomBase64);
|
||||
var uploadResponse = await client.PostAsJsonAsync("/api/v1/sbom/upload", request);
|
||||
Assert.Equal(HttpStatusCode.Accepted, uploadResponse.StatusCode);
|
||||
|
||||
var payload = await uploadResponse.Content.ReadFromJsonAsync<SbomUploadResponseDto>();
|
||||
Assert.NotNull(payload);
|
||||
Assert.Equal("spdx", payload!.Format);
|
||||
Assert.Equal("2.3", payload.FormatVersion);
|
||||
Assert.True(payload.ValidationResult.Valid);
|
||||
Assert.Equal(1, payload.ValidationResult.ComponentCount);
|
||||
Assert.Equal(1.0d, payload.ValidationResult.QualityScore);
|
||||
Assert.False(string.IsNullOrWhiteSpace(payload.AnalysisJobId));
|
||||
Assert.StartsWith("sha256:", payload.Digest, StringComparison.Ordinal);
|
||||
|
||||
var getResponse = await client.GetAsync($"/api/v1/sbom/uploads/{payload.SbomId}");
|
||||
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
|
||||
|
||||
var record = await getResponse.Content.ReadFromJsonAsync<SbomUploadRecordDto>();
|
||||
Assert.NotNull(record);
|
||||
Assert.Equal("build-77", record!.Source?.CiContext?.BuildId);
|
||||
Assert.Equal("example/service-repo", record.Source?.CiContext?.Repository);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
[Fact]
|
||||
public async Task Upload_rejects_unknown_format()
|
||||
{
|
||||
using var secrets = new TestSurfaceSecretsScope();
|
||||
@@ -87,7 +170,7 @@ public sealed class SbomUploadEndpointsTests
|
||||
var invalid = new SbomUploadRequestDto
|
||||
{
|
||||
ArtifactRef = "example.com/invalid:1.0",
|
||||
SbomBase64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("{\"name\":\"oops\"}"))
|
||||
SbomBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"name\":\"oops\"}"))
|
||||
};
|
||||
|
||||
var response = await client.PostAsJsonAsync("/api/v1/sbom/upload", invalid);
|
||||
@@ -109,38 +192,6 @@ public sealed class SbomUploadEndpointsTests
|
||||
return factory;
|
||||
}
|
||||
|
||||
private static string LoadFixtureBase64(string fileName)
|
||||
{
|
||||
var repoRoot = ResolveRepoRoot();
|
||||
var path = Path.Combine(
|
||||
repoRoot,
|
||||
"src",
|
||||
"AirGap",
|
||||
"__Tests",
|
||||
"StellaOps.AirGap.Importer.Tests",
|
||||
"Reconciliation",
|
||||
"Fixtures",
|
||||
fileName);
|
||||
|
||||
Assert.True(File.Exists(path), $"Fixture not found at {path}.");
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
private static string ResolveRepoRoot()
|
||||
{
|
||||
var baseDirectory = AppContext.BaseDirectory;
|
||||
return Path.GetFullPath(Path.Combine(
|
||||
baseDirectory,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
".."));
|
||||
}
|
||||
|
||||
private sealed class InMemoryArtifactObjectStore : IArtifactObjectStore
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, byte[]> _objects = new(StringComparer.Ordinal);
|
||||
|
||||
@@ -153,7 +153,28 @@ public sealed partial class ScansEndpointsTests
|
||||
ImmutableArray<EntryTraceEdge>.Empty,
|
||||
ImmutableArray<EntryTraceDiagnostic>.Empty,
|
||||
ImmutableArray.Create(plan),
|
||||
ImmutableArray.Create(terminal));
|
||||
ImmutableArray.Create(terminal),
|
||||
new EntryTraceBinaryIntelligence(
|
||||
ImmutableArray.Create(new EntryTraceBinaryTarget(
|
||||
"/usr/local/bin/app",
|
||||
"sha256:abc",
|
||||
"X64",
|
||||
"ELF",
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
ImmutableArray.Create(new EntryTraceBinaryVulnerability(
|
||||
"CVE-2024-1234",
|
||||
"SSL_read",
|
||||
"pkg:generic/openssl",
|
||||
"SSL_read",
|
||||
0.98f,
|
||||
"Critical")))),
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
generatedAt));
|
||||
|
||||
var ndjson = EntryTraceNdjsonWriter.Serialize(graph, new EntryTraceNdjsonMetadata(scanId, "sha256:test", generatedAt));
|
||||
var storedResult = new EntryTraceResult(scanId, "sha256:test", generatedAt, graph, ndjson);
|
||||
@@ -173,6 +194,8 @@ public sealed partial class ScansEndpointsTests
|
||||
Assert.Equal(storedResult.ScanId, payload!.ScanId);
|
||||
Assert.Equal(storedResult.ImageDigest, payload.ImageDigest);
|
||||
Assert.Equal(storedResult.Graph.Plans.Length, payload.Graph.Plans.Length);
|
||||
Assert.NotNull(payload.Graph.BinaryIntelligence);
|
||||
Assert.Equal(1, payload.Graph.BinaryIntelligence!.TotalVulnerableMatches);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
|
||||
@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| QA-SCANNER-VERIFY-008 | DONE | `SPRINT_20260212_002_Scanner_unchecked_feature_verification_batch1.md`: extended entry-trace endpoint contract test assertions for `graph.binaryIntelligence`; verified in run-002 Tier 2 (2026-02-12). |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Scanner/__Tests/StellaOps.Scanner.WebService.Tests/StellaOps.Scanner.WebService.Tests.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
| SPRINT-20260208-062-VEXREACH-001 | DONE | Added deterministic unit coverage for VEX+reachability filter matrix and controller endpoint (`6` tests passed on filtered run, 2026-02-08). |
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using StellaOps.BinaryIndex.Core.Models;
|
||||
using StellaOps.BinaryIndex.Core.Services;
|
||||
using StellaOps.Scanner.Analyzers.Native.Index;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
using StellaOps.Scanner.PatchVerification;
|
||||
using StellaOps.Scanner.PatchVerification.Models;
|
||||
using StellaOps.Scanner.Worker.Extensions;
|
||||
using StellaOps.Scanner.Worker.Processing;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
using BinaryFormat = StellaOps.BinaryIndex.Core.Models.BinaryFormat;
|
||||
|
||||
namespace StellaOps.Scanner.Worker.Tests;
|
||||
|
||||
public sealed class BinaryLookupStageExecutorTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WiresBuildIdLookupPatchVerificationAndMappedFindings()
|
||||
{
|
||||
const string scanId = "11111111-1111-1111-1111-111111111111";
|
||||
|
||||
var identity = new BinaryIdentity
|
||||
{
|
||||
BinaryKey = "gnu-build-id:abc123:sha256:deadbeef",
|
||||
BuildId = "gnu-build-id:abc123",
|
||||
BuildIdType = "gnu-build-id",
|
||||
FileSha256 = "sha256:deadbeef",
|
||||
Format = BinaryFormat.Elf,
|
||||
Architecture = "x86_64"
|
||||
};
|
||||
|
||||
var vulnMatch = new BinaryVulnMatch
|
||||
{
|
||||
CveId = "CVE-2026-1234",
|
||||
VulnerablePurl = "pkg:deb/debian/libssl@1.1.1",
|
||||
Method = MatchMethod.BuildIdCatalog,
|
||||
Confidence = 0.97m,
|
||||
Evidence = new MatchEvidence { BuildId = "gnu-build-id:abc123" }
|
||||
};
|
||||
|
||||
var vulnService = new Mock<IBinaryVulnerabilityService>(MockBehavior.Strict);
|
||||
vulnService
|
||||
.Setup(service => service.LookupBatchAsync(
|
||||
It.IsAny<IEnumerable<BinaryIdentity>>(),
|
||||
It.IsAny<LookupOptions?>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ImmutableDictionary<string, ImmutableArray<BinaryVulnMatch>>.Empty.Add(identity.BinaryKey, [vulnMatch]));
|
||||
vulnService
|
||||
.Setup(service => service.GetFixStatusBatchAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<IEnumerable<string>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(ImmutableDictionary<string, FixStatusResult>.Empty);
|
||||
|
||||
var extractor = new Mock<IBinaryFeatureExtractor>(MockBehavior.Strict);
|
||||
extractor
|
||||
.Setup(e => e.ExtractIdentityAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(identity);
|
||||
|
||||
var buildIdIndex = new Mock<IBuildIdIndex>(MockBehavior.Strict);
|
||||
buildIdIndex.SetupGet(index => index.IsLoaded).Returns(false);
|
||||
buildIdIndex.SetupGet(index => index.Count).Returns(1);
|
||||
buildIdIndex
|
||||
.Setup(index => index.LoadAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
buildIdIndex
|
||||
.Setup(index => index.BatchLookupAsync(It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(
|
||||
[
|
||||
new BuildIdLookupResult(
|
||||
"gnu-build-id:abc123",
|
||||
"pkg:deb/debian/libssl@1.1.1",
|
||||
"1.1.1",
|
||||
"debian",
|
||||
BuildIdConfidence.Exact,
|
||||
DateTimeOffset.UtcNow)
|
||||
]);
|
||||
buildIdIndex
|
||||
.Setup(index => index.LookupAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((BuildIdLookupResult?)null);
|
||||
|
||||
var patchVerification = new Mock<IPatchVerificationOrchestrator>(MockBehavior.Strict);
|
||||
patchVerification
|
||||
.Setup(orchestrator => orchestrator.VerifyAsync(
|
||||
It.IsAny<PatchVerificationContext>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new PatchVerificationResult
|
||||
{
|
||||
ScanId = scanId,
|
||||
Evidence = [],
|
||||
PatchedCves = ImmutableHashSet<string>.Empty,
|
||||
UnpatchedCves = ImmutableHashSet<string>.Empty,
|
||||
InconclusiveCves = ImmutableHashSet<string>.Empty,
|
||||
NoPatchDataCves = ImmutableHashSet<string>.Empty,
|
||||
VerifiedAt = DateTimeOffset.UtcNow,
|
||||
VerifierVersion = PatchVerificationOrchestrator.VerifierVersion
|
||||
});
|
||||
|
||||
await using var scopedProvider = new ServiceCollection()
|
||||
.AddSingleton(patchVerification.Object)
|
||||
.BuildServiceProvider();
|
||||
|
||||
var analyzer = new BinaryVulnerabilityAnalyzer(
|
||||
vulnService.Object,
|
||||
extractor.Object,
|
||||
NullLogger<BinaryVulnerabilityAnalyzer>.Instance);
|
||||
|
||||
var findingMapper = new BinaryFindingMapper(
|
||||
vulnService.Object,
|
||||
NullLogger<BinaryFindingMapper>.Instance);
|
||||
|
||||
var stage = new BinaryLookupStageExecutor(
|
||||
analyzer,
|
||||
findingMapper,
|
||||
buildIdIndex.Object,
|
||||
scopedProvider.GetRequiredService<IServiceScopeFactory>(),
|
||||
new BinaryIndexOptions { Enabled = true },
|
||||
NullLogger<BinaryLookupStageExecutor>.Instance);
|
||||
|
||||
var metadata = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
[ScanMetadataKeys.RootFilesystemPath] = "/tmp/rootfs"
|
||||
};
|
||||
|
||||
var lease = new TestLease(metadata, "job-1", scanId);
|
||||
var context = new ScanJobContext(lease, TimeProvider.System, TimeProvider.System.GetUtcNow(), TestContext.Current.CancellationToken);
|
||||
|
||||
context.Analysis.Set("layers", new List<LayerInfo>
|
||||
{
|
||||
new() { Digest = "sha256:layer1", MediaType = "application/vnd.oci.image.layer.v1.tar", Size = 123 }
|
||||
});
|
||||
context.Analysis.Set("binary_paths_sha256:layer1", new List<string> { "/usr/lib/libssl.so" });
|
||||
context.Analysis.Set("detected_distro", "debian");
|
||||
context.Analysis.Set("detected_release", "12");
|
||||
context.Analysis.Set<Func<string, string, Stream?>>(
|
||||
"layer_file_opener",
|
||||
(_, _) => new MemoryStream([0x7F, 0x45, 0x4C, 0x46, 0x02, 0x01]));
|
||||
|
||||
await stage.ExecuteAsync(context, TestContext.Current.CancellationToken);
|
||||
|
||||
var rawFindings = context.Analysis.GetBinaryFindings();
|
||||
Assert.Single(rawFindings);
|
||||
Assert.Equal("CVE-2026-1234", rawFindings[0].CveId);
|
||||
|
||||
Assert.True(context.Analysis.TryGet<IReadOnlyList<object>>(ScanAnalysisKeys.BinaryVulnerabilityFindings, out var mapped));
|
||||
Assert.Single(mapped);
|
||||
|
||||
Assert.True(context.Analysis.TryGet<IReadOnlyDictionary<string, BuildIdLookupResult>>(ScanAnalysisKeys.BinaryBuildIdMappings, out var mappings));
|
||||
Assert.True(mappings.ContainsKey("gnu-build-id:abc123"));
|
||||
|
||||
Assert.True(context.Analysis.TryGet<PatchVerificationResult>(ScanAnalysisKeys.BinaryPatchVerificationResult, out var patchResult));
|
||||
Assert.Equal(scanId, patchResult.ScanId);
|
||||
|
||||
buildIdIndex.Verify(index => index.LoadAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
buildIdIndex.Verify(index => index.BatchLookupAsync(
|
||||
It.Is<IEnumerable<string>>(ids => ids.Contains("gnu-build-id:abc123")),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
patchVerification.Verify(orchestrator => orchestrator.VerifyAsync(
|
||||
It.Is<PatchVerificationContext>(ctx => ctx.CveIds.Contains("CVE-2026-1234")),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
private sealed class TestLease : IScanJobLease
|
||||
{
|
||||
public TestLease(IReadOnlyDictionary<string, string> metadata, string jobId, string scanId)
|
||||
{
|
||||
Metadata = metadata;
|
||||
JobId = jobId;
|
||||
ScanId = scanId;
|
||||
Attempt = 1;
|
||||
EnqueuedAtUtc = DateTimeOffset.UtcNow;
|
||||
LeasedAtUtc = DateTimeOffset.UtcNow;
|
||||
LeaseDuration = TimeSpan.FromMinutes(1);
|
||||
}
|
||||
|
||||
public string JobId { get; }
|
||||
public string ScanId { get; }
|
||||
public int Attempt { get; }
|
||||
public DateTimeOffset EnqueuedAtUtc { get; }
|
||||
public DateTimeOffset LeasedAtUtc { get; }
|
||||
public TimeSpan LeaseDuration { get; }
|
||||
public IReadOnlyDictionary<string, string> Metadata { get; }
|
||||
|
||||
public ValueTask RenewAsync(CancellationToken cancellationToken) => ValueTask.CompletedTask;
|
||||
public ValueTask CompleteAsync(CancellationToken cancellationToken) => ValueTask.CompletedTask;
|
||||
public ValueTask AbandonAsync(string reason, CancellationToken cancellationToken) => ValueTask.CompletedTask;
|
||||
public ValueTask PoisonAsync(string reason, CancellationToken cancellationToken) => ValueTask.CompletedTask;
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,58 @@ public sealed class EntryTraceExecutionServiceTests : IDisposable
|
||||
Assert.Equal(ndjsonPayload, store.LastResult.Ndjson);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_AddsBinaryIntelligence_ForNativeTerminal()
|
||||
{
|
||||
var metadata = CreateMetadata("PATH=/bin:/usr/bin");
|
||||
var rootDirectory = metadata[ScanMetadataKeys.RootFilesystemPath];
|
||||
var binaryPath = Path.Combine(rootDirectory, "usr", "bin", "app");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(binaryPath)!);
|
||||
File.WriteAllBytes(binaryPath, CreateElfPayloadWithMarker("CVE-2024-9999"));
|
||||
|
||||
var graph = new EntryTraceGraph(
|
||||
EntryTraceOutcome.Resolved,
|
||||
ImmutableArray<EntryTraceNode>.Empty,
|
||||
ImmutableArray<EntryTraceEdge>.Empty,
|
||||
ImmutableArray<EntryTraceDiagnostic>.Empty,
|
||||
ImmutableArray.Create(new EntryTracePlan(
|
||||
ImmutableArray.Create("/usr/bin/app"),
|
||||
ImmutableDictionary<string, string>.Empty,
|
||||
"/workspace",
|
||||
"scanner",
|
||||
"/usr/bin/app",
|
||||
EntryTraceTerminalType.Native,
|
||||
null,
|
||||
0.9,
|
||||
ImmutableDictionary<string, string>.Empty)),
|
||||
ImmutableArray.Create(new EntryTraceTerminal(
|
||||
"/usr/bin/app",
|
||||
EntryTraceTerminalType.Native,
|
||||
null,
|
||||
0.9,
|
||||
ImmutableDictionary<string, string>.Empty,
|
||||
"scanner",
|
||||
"/workspace",
|
||||
ImmutableArray<string>.Empty)));
|
||||
|
||||
var analyzer = new CapturingEntryTraceAnalyzer(graph);
|
||||
var store = new CapturingEntryTraceResultStore();
|
||||
var service = CreateService(analyzer, store);
|
||||
|
||||
await service.ExecuteAsync(CreateContext(metadata), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(store.Stored);
|
||||
Assert.NotNull(store.LastResult);
|
||||
Assert.NotNull(store.LastResult!.Graph.BinaryIntelligence);
|
||||
Assert.Equal(1, store.LastResult.Graph.BinaryIntelligence!.TotalTargets);
|
||||
Assert.Equal(1, store.LastResult.Graph.BinaryIntelligence.AnalyzedTargets);
|
||||
Assert.Single(store.LastResult.Graph.BinaryIntelligence.Targets);
|
||||
Assert.Contains(
|
||||
store.LastResult.Graph.BinaryIntelligence.Targets[0].VulnerableMatches,
|
||||
match => string.Equals(match.VulnerabilityId, "CVE-2024-9999", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
@@ -273,19 +325,24 @@ public sealed class EntryTraceExecutionServiceTests : IDisposable
|
||||
|
||||
private sealed class CapturingEntryTraceAnalyzer : IEntryTraceAnalyzer
|
||||
{
|
||||
public CapturingEntryTraceAnalyzer(EntryTraceGraph? graph = null)
|
||||
{
|
||||
Graph = graph ?? new EntryTraceGraph(
|
||||
EntryTraceOutcome.Resolved,
|
||||
ImmutableArray<EntryTraceNode>.Empty,
|
||||
ImmutableArray<EntryTraceEdge>.Empty,
|
||||
ImmutableArray<EntryTraceDiagnostic>.Empty,
|
||||
ImmutableArray<EntryTracePlan>.Empty,
|
||||
ImmutableArray<EntryTraceTerminal>.Empty);
|
||||
}
|
||||
|
||||
public bool Invoked { get; private set; }
|
||||
|
||||
public EntrypointSpecification? LastEntrypoint { get; private set; }
|
||||
|
||||
public EntryTraceContext? LastContext { get; private set; }
|
||||
|
||||
public EntryTraceGraph Graph { get; } = new(
|
||||
EntryTraceOutcome.Resolved,
|
||||
ImmutableArray<EntryTraceNode>.Empty,
|
||||
ImmutableArray<EntryTraceEdge>.Empty,
|
||||
ImmutableArray<EntryTraceDiagnostic>.Empty,
|
||||
ImmutableArray<EntryTracePlan>.Empty,
|
||||
ImmutableArray<EntryTraceTerminal>.Empty);
|
||||
public EntryTraceGraph Graph { get; }
|
||||
|
||||
public ValueTask<EntryTraceGraph> ResolveAsync(EntrypointSpecification entrypoint, EntryTraceContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -303,6 +360,26 @@ public sealed class EntryTraceExecutionServiceTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] CreateElfPayloadWithMarker(string marker)
|
||||
{
|
||||
var prefix = new byte[]
|
||||
{
|
||||
0x7F, 0x45, 0x4C, 0x46, // ELF
|
||||
0x02, 0x01, 0x01, 0x00, // 64-bit, little-endian
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, // e_type
|
||||
0x3E, 0x00, // x64 machine
|
||||
0x01, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
var markerBytes = Encoding.ASCII.GetBytes($"SSL_read\0{marker}\0");
|
||||
var payload = new byte[512];
|
||||
Buffer.BlockCopy(prefix, 0, payload, 0, prefix.Length);
|
||||
Buffer.BlockCopy(markerBytes, 0, payload, 128, markerBytes.Length);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
private sealed class CapturingEntryTraceResultStore : IEntryTraceResultStore
|
||||
{
|
||||
public bool Stored { get; private set; }
|
||||
|
||||
@@ -4,6 +4,8 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| QA-SCANNER-VERIFY-009 | DONE | `SPRINT_20260212_002_Scanner_unchecked_feature_verification_batch1.md`: added deterministic `BinaryLookupStageExecutorTests` coverage for runtime patch verification, Build-ID mapping, and unified finding publication wiring (run-002, 2026-02-12). |
|
||||
| QA-SCANNER-VERIFY-008 | DONE | `SPRINT_20260212_002_Scanner_unchecked_feature_verification_batch1.md`: added worker entry-trace execution coverage for binary intelligence graph enrichment and validated run-002 pass (2026-02-12). |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Scanner/__Tests/StellaOps.Scanner.Worker.Tests/StellaOps.Scanner.Worker.Tests.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
| SPRINT-20260208-060-IDEMP-001 | DONE | Implement idempotent verdict attestation submission (idempotency key + dedupe + retry classification + tests). |
|
||||
|
||||
Reference in New Issue
Block a user